﻿using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace Pumpkin.Utils
{
    public abstract class TriggerBase<Key, TAction>
    {
        protected sealed class Event : IDisposable
        {
            private const int kOnceMaxInvokeCount = 80;
            private int onceInvokeCount_;
            public TAction Trigger;

            public void Dispose()
            {
                onceInvokeCount_ = 0;
            }

            public bool CheckInvokeCount()
            {
                if (onceInvokeCount_ > kOnceMaxInvokeCount)
                {
                    throw new ArgumentOutOfRangeException("Trigger invoke too many at once");
                }
                ++onceInvokeCount_;
                return onceInvokeCount_ == 1;
            }
        }

        private Dictionary<Key, Event> triggers_ = new Dictionary<Key, Event>();
        protected abstract void Combine(Event e, TAction trigger);
        protected abstract void Remove(Event e, TAction trigger);

        public bool HasTriggerEvent(Key type)
        {
            return triggers_.ContainsKey(type);
        }

        public void AddTriggerEvent(Key type, TAction trigger)
        {
            Event e;
            if (triggers_.TryGetValue(type, out e))
            {
                Combine(e, trigger);
            }
            else
            {
                triggers_.Add(type, new Event() { Trigger = trigger });
            }
        }

        public void RemoveTriggerEvent(Key type, TAction trigger)
        {
            Event e;
            if (triggers_.TryGetValue(type, out e))
            {
                Remove(e, trigger);
                if (e.Trigger == null)
                {
                    triggers_.Remove(type);
                }
            }
        }

        public void RemoveTrigger(Key type)
        {
            triggers_.Remove(type);
        }

        public void RemoveAllTriggers()
        {
            triggers_.Clear();
        }

        protected IDisposable BeginInvoke(Key type, out TAction action)
        {
            Event e;
            if (triggers_.TryGetValue(type, out e))
            {
                bool isFirst = e.CheckInvokeCount();
                action = e.Trigger;
                return isFirst ? e : null;
            }
            else
            {
                action = default(TAction);
                return null;
            }
        }
    }

    public class Trigger<Key, T1, T2> : TriggerBase<Key, Action<T1, T2>>
    {
        protected override void Combine(Event e, Action<T1, T2> trigger)
        {
            e.Trigger += trigger;
        }

        protected override void Remove(Event e, Action<T1, T2> trigger)
        {
            e.Trigger -= trigger;
        }

        public void InvokeTrigger(Key type, T1 param1 = default(T1), T2 param2 = default(T2))
        {
            Action<T1, T2> action;
            using (BeginInvoke(type, out action))
            {
                if (action != null)
                {
                    action.Invoke(param1, param2);
                }
            }
        }
    }
}
